using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using OpenBlock.Terrain.Generators;
using OpenBlock.Entity.Player;
using OpenBlock.IO;
using OpenBlock.Utils;

namespace OpenBlock.Terrain
{
    public class WorldObj : MonoBehaviour
    {
        public const int LOAD_HEIGHT_RADIUS = 2;
        public const int LOAD_HEIGHT_OFFSET = 1;
        public const int LOAD_RADIUS = 3;
        public const int BUILD_CHUNK_PER_FRAME = 1;

        [SerializeField]
        private ChunkObj chunkObjPrefab;
        [SerializeField]
        private Transform playerTransform;

        public Level level { get; private set; }
        public PlayerData playerData { get; private set; }
        private List<ChunkObj> chunkObjs = new List<ChunkObj>();
        private Pool<ChunkObj> chunkObjPool = new Pool<ChunkObj>();
        private Queue<ChunkObj> rebuildQueue = new Queue<ChunkObj>();

        private Vector3Int prevPlayerChunkPos;

        private void Awake()
        {
            //level = new Level(new SkyIslandGenerator((int)System.DateTime.Now.Ticks));
            var info = JsonUtility.FromJson<LevelInfo>(File.ReadAllText(Path.Combine(LevelManager.LevelDirectoryPath, "Test", "level_info.json")));
            level = new Level(info.levelName, info.seed, info.type);
        }

        private void Start()
        {
            prevPlayerChunkPos = MathUtils.BlockPos2ChunkPos(MathUtils.AsBlockPos(playerTransform.position));

            level.LoadChunks(prevPlayerChunkPos);
            LoadChunkObjs(prevPlayerChunkPos);
        }

        private void Update()
        {
            var playerChunkPos = MathUtils.BlockPos2ChunkPos(MathUtils.AsBlockPos(playerTransform.position));

            if (MathUtils.ManhattanDistance(prevPlayerChunkPos, playerChunkPos) >= 2)
            {
                level.LoadChunks(playerChunkPos);
                LoadChunkObjs(playerChunkPos);
                prevPlayerChunkPos = playerChunkPos;
            }

            for (int i = 0; i < BUILD_CHUNK_PER_FRAME && rebuildQueue.Count != 0; i++)
            {
                var chunk = rebuildQueue.Dequeue();
                chunk.Rebuild();
            }
        }

        private void LateUpdate()
        {
            for (int i = 0; i < chunkObjs.Count; i++)
            {
                var chunk = chunkObjs[i];
                if (chunk.pChunk == null || chunk.pChunk.status.HasFlag(Chunk.ChunkStatus.Loading)) continue;

                if (chunk.pChunk.status.HasFlag(Chunk.ChunkStatus.Inactive))
                {
                    chunk.UnbindChunk();
                    chunk.gameObject.SetActive(false);

                    var t = chunkObjs[i];
                    chunkObjs[i] = chunkObjs[chunkObjs.Count - 1];
                    chunkObjs[chunkObjs.Count - 1] = t;
                    chunkObjs.RemoveAt(chunkObjs.Count - 1);

                    chunkObjPool.Free(chunk);
                } 
                else if (chunk.pChunk.status.HasFlag(Chunk.ChunkStatus.NeedRebuild))
                {
                    rebuildQueue.Enqueue(chunk);
                }
            }
        }

        private void OnDisable()
        {
            level.UnloadAllChunk();
        }

        // Traverse chunks in load range. If any Chunk not bound, bind it to ChunkObj.
        // And only enable collide in manhattan distance in 2.
        private void LoadChunkObjs(Vector3Int center)
        {
            int xStart = center.x - LOAD_RADIUS;
            int xEnd = center.x + LOAD_RADIUS;
            int yStart = center.y - LOAD_HEIGHT_RADIUS + LOAD_HEIGHT_OFFSET;
            int yEnd = center.y + LOAD_HEIGHT_RADIUS + LOAD_HEIGHT_OFFSET;
            int zStart = center.z - LOAD_RADIUS;
            int zEnd = center.z + LOAD_RADIUS;

            for (int x = xStart; x < xEnd; x++)
            {
                for (int y = yStart; y < yEnd; y++)
                {
                    for (int z = zStart; z < zEnd; z++)
                    {
                        var chunkPos = new Vector3Int(x, y, z);
                        if (level.GetChunk(chunkPos) is Chunk ch)
                        {
                            if (BindChunkObj(ch, chunkPos == center) is ChunkObj obj)
                            {
                                if (MathUtils.ManhattanDistance(chunkPos, center) <= 2)
                                {
                                    obj.EnableCollider();
                                }
                                else
                                {
                                    obj.DisableCollider();
                                }
                            }
                        }
                    }
                }
            }
        }

        private ChunkObj BindChunkObj(Chunk ch, bool forceAdd)
        {
            if (!forceAdd && ch.IsEmpty()) return null;

            foreach (var chObj in chunkObjs)
            {
                if (chObj.pChunk == ch) return chObj;
            }

            ChunkObj chunkObj = chunkObjPool.ObtainOrCreate(() =>
            {
                var obj = Instantiate(chunkObjPrefab);
                obj.transform.SetParent(transform);
                return obj;
            });

            chunkObj.gameObject.SetActive(true);
            chunkObjs.Add(chunkObj);
            chunkObj.BindChunk(ch);

            return chunkObj;
        }
    }
}
